// DetectFxRegistry.cpp  Version 1.0
//
// Original Author:  Aaron Stebner
//                   http://astebner.sts.winisp.net/Tools/detectFX.cpp.txt
//
// Modified by:      Hans Dietrich
//                   hdietrich@gmail.com
// Modified by:		 Thomas Maierhofer
//                   thomas@maierhofer.de
//
// Description:
//     DetectFxRegistry.cpp implements CDetectFxRegistry, a class to check for
//     .Net installations in the registry.
//
// Usage Notes:
//     You will see the line "#ifdef XLISTBOX_H" throughout this code. The 
//     purpose of this is to facilitate logging at low level in detection 
//     process.  If you do not want this logging - e.g., to use this class
//     simply to detect .Net Framework - simply comment out the line
//           #include "XListBox.h"
//     in DetectFxRegistry.h.
//
// History
//     Version 1.0 - 2007 June 8
//     - Initial public release
//
// Public APIs:
//             NAME                            DESCRIPTION
//     -------------------------    -----------------------------------------
//     CheckNetfx30BuildNumber()    Validate .NET 3.0 build number 
//     CheckNetfx35BuildNumber()    Validate .NET 3.5 build number 
//     GetNetfx10SPLevel()          Get service pack level for .Net 1.0
//     GetNetfx11SPLevel()          Get service pack level for .Net 1.1
//     GetNetfx20SPLevel()          Get service pack level for .Net 2.0
//     GetNetfx30SPLevel()          Get service pack level for .Net 3.0
//     GetNetfx35SPLevel()          Get service pack level for .Net 3.5
//     IsNetfx10Installed()         Check for .Net 1.0
//     IsNetfx11Installed()         Check for .Net 1.1
//     IsNetfx20Installed()         Check for .Net 2.0
//     IsNetfx30Installed()         Check for .Net 3.0
//     IsNetfx35Installed()         Check for .Net 3.5
//
// License:
//     This software is released into the public domain.  You are free to use
//     it in any way you like, except that you may not sell this source code.
//
//     This software is provided "as is" with no expressed or implied warranty.
//     I accept no liability for any damage or loss of business that this 
//     software may cause.
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "SetupBootstrapLib.h"
#include "SetupBootstrapLibInternal.h"

// In case the machine this is compiled on does not have the most recent platform SDK
// with these values defined, define them here
#ifndef SM_TABLETPC
	#define SM_TABLETPC		86
#endif

#ifndef SM_MEDIACENTER
	#define SM_MEDIACENTER	87
#endif

// Constants that represent registry key names and value names
// to use for detection
static const TCHAR *g_szNetfx10RegKeyName = _T("Software\\Microsoft\\.NETFramework\\Policy\\v1.0");
static const TCHAR *g_szNetfx10RegKeyValue = _T("3705");
static const TCHAR *g_szNetfx10SPxMSIRegKeyName = _T("Software\\Microsoft\\Active Setup\\Installed Components\\{78705f0d-e8db-4b2d-8193-982bdda15ecd}");
static const TCHAR *g_szNetfx10SPxOCMRegKeyName = _T("Software\\Microsoft\\Active Setup\\Installed Components\\{FDC11A6F-17D1-48f9-9EA3-9051954BAA24}");
static const TCHAR *g_szNetfx10SPxRegValueName = _T("Version");

static const TCHAR *g_szNetfx11RegKeyName = _T("Software\\Microsoft\\NET Framework Setup\\NDP\\v1.1.4322");
static const TCHAR *g_szNetfx20RegKeyName = _T("Software\\Microsoft\\NET Framework Setup\\NDP\\v2.0.50727");
static const TCHAR *g_szNetfx11and20RegValueName = _T("Install");
static const TCHAR *g_szNetfx11and20SPxRegValueName = _T("SP");

static const TCHAR *g_szNetfx30RegKeyName = _T("Software\\Microsoft\\NET Framework Setup\\NDP\\v3.0\\Setup");
static const TCHAR *g_szNetfx30RegValueName = _T("InstallSuccess");
static const TCHAR *g_szNetfx30VersionRegValueName = _T("Version");
static const TCHAR *g_szNetfx30SPxRegValueName = _T("SP");

static const TCHAR *g_szNetfx35RegKeyName = _T("Software\\Microsoft\\NET Framework Setup\\NDP\\v3.5");
static const TCHAR *g_szNetfx35RegValueName = _T("Install");
static const TCHAR *g_szNetfx35VersionRegValueName = _T("Version");		// 3.5.20404.00
static const TCHAR *g_szNetfx35SPxRegValueName = _T("SP");

static const int g_nNetfx30VersionMajor = 3;
static const int g_nNetfx30VersionMinor = 0;
static const int g_nNetfx30BuildNumber = 4506;
static const int g_nNetfx30RevisionNumber = 26;
static const int g_nNetfx30RevisionNumber2 = 57;

static const int g_nNetfx35VersionMajor = 3;
static const int g_nNetfx35VersionMinor = 5;
static const int g_nNetfx35BuildNumber = 20404;
static const int g_nNetfx35RevisionNumber = 0;

// Function prototypes

#ifdef XLISTBOX_H
CDetectFxRegistry::CDetectFxRegistry(LPCTSTR lpszComputer, CXListBox * pList /*= NULL*/)
  : m_pList(pList)
#else
CDetectFxRegistry::CDetectFxRegistry(LPCTSTR lpszComputer)
#endif
{
	ZeroMemory(m_szComputer, sizeof(m_szComputer));
	_tcsncpy(m_szComputer, lpszComputer, sizeof(m_szComputer)/sizeof(TCHAR)-2);
}

/******************************************************************
Function Name:	IsNetfx10Installed
Description:	Uses the detection method recommended at
                http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetdep/html/dotnetfxref.asp
                to determine whether the .NET Framework 1.0 is
                installed on the machine
Inputs:	        NONE
Results:        true if the .NET Framework 1.0 is installed
                false otherwise
******************************************************************/
bool CDetectFxRegistry::IsNetfx10Installed()
{
	TCHAR szRegValue[MAX_PATH] = { _T('\0') };
	return (RegistryGetValue(HKEY_LOCAL_MACHINE, 
							 g_szNetfx10RegKeyName, 
							 g_szNetfx10RegKeyValue, 
							 NULL, 
							 (LPBYTE)szRegValue, 
							 sizeof(szRegValue)-2));
}


/******************************************************************
Function Name:	IsNetfx11Installed
Description:	Uses the detection method recommended at
                http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetdep/html/redistdeploy1_1.asp
                to determine whether the .NET Framework 1.1 is
                installed on the machine
Inputs:	        NONE
Results:        true if the .NET Framework 1.1 is installed
                false otherwise
******************************************************************/
bool CDetectFxRegistry::IsNetfx11Installed()
{
	bool rc = false;
	DWORD dwRegValue = 0;

	if (RegistryGetValue(HKEY_LOCAL_MACHINE, 
						 g_szNetfx11RegKeyName, 
						 g_szNetfx11and20RegValueName, 
						 NULL, 
						 (LPBYTE)&dwRegValue, 
						 sizeof(DWORD)))
	{
		if (1 == dwRegValue)
			rc = true;
	}

	return rc;
}


/******************************************************************
Function Name:	IsNetfx20Installed
Description:	Uses the detection method recommended at
                http://msdn2.microsoft.com/en-us/library/aa480243.aspx
                to determine whether the .NET Framework 2.0 is
                installed on the machine
Inputs:	        NONE
Results:        true if the .NET Framework 2.0 is installed
                false otherwise
******************************************************************/
bool CDetectFxRegistry::IsNetfx20Installed()
{
	bool rc = false;
	DWORD dwRegValue = 0;

	if (RegistryGetValue(HKEY_LOCAL_MACHINE, 
						 g_szNetfx20RegKeyName, 
						 g_szNetfx11and20RegValueName, 
						 NULL, 
						 (LPBYTE)&dwRegValue, 
						 sizeof(DWORD)))
	{
		if (1 == dwRegValue)
			rc = true;
	}

	return rc;
}


/******************************************************************
Function Name:	IsNetfx30Installed
Description:	Uses the detection method recommended at
                http://msdn2.microsoft.com/en-us/library/aa480173.aspx
                to determine whether the .NET Framework 3.0 is
                installed on the machine
Inputs:	        NONE
Results:        true if the .NET Framework 3.0 is installed
                false otherwise
******************************************************************/
bool CDetectFxRegistry::IsNetfx30Installed()
{
	bool rc = false;
	DWORD dwRegValue = 0;

	// Check that the InstallSuccess registry value exists and equals 1
	if (RegistryGetValue(HKEY_LOCAL_MACHINE, 
						 g_szNetfx30RegKeyName, 
						 g_szNetfx30RegValueName, 
						 NULL, 
						 (LPBYTE)&dwRegValue, 
						 sizeof(DWORD)))
	{
		if (1 == dwRegValue)
			rc = true;
	}

	// A system with a pre-release version of the .NET Framework 3.0 can
	// have the InstallSuccess value.  As an added verification, check the
	// version number listed in the registry
	return (rc && CheckNetfx30BuildNumber());
}


/******************************************************************
Function Name:	IsNetfx35Installed
Description:	Uses the detection method recommended at
                http://msdn2.microsoft.com/en-us/library/aa480173.aspx
                to determine whether the .NET Framework 3.0 is
                installed on the machine
Inputs:	        NONE
Results:        true if the .NET Framework 3.5 is installed
                false otherwise
******************************************************************/
bool CDetectFxRegistry::IsNetfx35Installed()
{
	bool rc = false;
	DWORD dwRegValue = 0;

	// Check that the Install registry value exists and equals 1
	if (RegistryGetValue(HKEY_LOCAL_MACHINE, 
						 g_szNetfx35RegKeyName, 
						 g_szNetfx35RegValueName, 
						 NULL, 
						 (LPBYTE)&dwRegValue, 
						 sizeof(DWORD)))
	{
		if (1 == dwRegValue)
			rc = true;
	}

	// A system with a pre-release version of the .NET Framework 3.5 can
	// have the Install value.  As an added verification, check the
	// version number listed in the registry
	return (rc && CheckNetfx35BuildNumber());
}


/******************************************************************
Function Name:	GetNetfx10SPLevel
Description:	Uses the detection method recommended at
                http://blogs.msdn.com/astebner/archive/2004/09/14/229802.aspx
                to determine what service pack for the 
                .NET Framework 1.0 is installed on the machine
Inputs:	        NONE
Results:        integer representing SP level for .NET Framework 1.0
******************************************************************/
int CDetectFxRegistry::GetNetfx10SPLevel()
{
	TCHAR szRegValue[MAX_PATH] = { _T('\0') };
	TCHAR *pszSPLevel = NULL;
	int rc = -1;
	bool bRegistryRetVal = false;

	// Need to detect what OS we are running on so we know what
	// registry key to use to look up the SP level
	if (IsCurrentOSTabletMedCenter())
		bRegistryRetVal = RegistryGetValue(HKEY_LOCAL_MACHINE, 
										   g_szNetfx10SPxOCMRegKeyName, 
										   g_szNetfx10SPxRegValueName, 
										   NULL, 
										   (LPBYTE)szRegValue, 
										   sizeof(szRegValue)-2);
	else
		bRegistryRetVal = RegistryGetValue(HKEY_LOCAL_MACHINE, 
										   g_szNetfx10SPxMSIRegKeyName, 
										   g_szNetfx10SPxRegValueName, 
										   NULL, 
										   (LPBYTE)szRegValue, 
										   sizeof(szRegValue)-2);

	if (bRegistryRetVal)
	{
		// This registry value should be of the format
		// #,#,#####,# where the last # is the SP level
		// Try to parse off the last # here
		pszSPLevel = _tcsrchr(szRegValue, _T(','));
		if (pszSPLevel)
		{
			// Increment the pointer to skip the comma
			pszSPLevel++;

			// Convert the remaining value to an integer
			rc = _ttoi(pszSPLevel);
		}
	}

	return rc;
}


/******************************************************************
Function Name:	GetNetfx11SPLevel
Description:	Uses the detection method recommended at
                http://blogs.msdn.com/astebner/archive/2004/09/14/229574.aspx
                to determine what service pack for the 
                .NET Framework 1.1 is installed on the machine
Inputs:	        NONE
Results:        integer representing SP level for .NET Framework 1.1
******************************************************************/
int CDetectFxRegistry::GetNetfx11SPLevel()
{
	int rc = -1;
	DWORD dwRegValue = 0;

	if (RegistryGetValue(HKEY_LOCAL_MACHINE, 
						 g_szNetfx11RegKeyName, 
						 g_szNetfx11and20SPxRegValueName, 
						 NULL, 
						 (LPBYTE)&dwRegValue, 
						 sizeof(DWORD)))
	{
		rc = (int)dwRegValue;
	}

	// We return -1 if the .NET Framework 1.1 is not installed or there was 
	// some kind of error retrieving the data from the registry
	return rc;
}


/******************************************************************
Function Name:	GetNetfx20SPLevel
Description:	Uses the detection method recommended at
                http://blogs.msdn.com/astebner/archive/2004/09/14/229574.aspx
                to determine what service pack for the 
                .NET Framework 2.0 is installed on the machine
Inputs:         NONE
Results:        integer representing SP level for .NET Framework 2.0
******************************************************************/
int CDetectFxRegistry::GetNetfx20SPLevel()
{
	int rc = -1;
	DWORD dwRegValue = 0;

	if (RegistryGetValue(HKEY_LOCAL_MACHINE, 
						 g_szNetfx20RegKeyName, 
						 g_szNetfx11and20SPxRegValueName, 
						 NULL, 
						 (LPBYTE)&dwRegValue, 
						 sizeof(DWORD)))
	{
		rc = (int)dwRegValue;
	}

	// We return -1 if the .NET Framework 2.0 is not installed or there was 
	// some kind of error retrieving the data from the registry
	return rc;
}


/******************************************************************
Function Name:	GetNetfx30SPLevel
Description:	Uses the detection method recommended at
                http://blogs.msdn.com/astebner/archive/2004/09/14/229574.aspx
                to determine what service pack for the 
                .NET Framework 3.0 is installed on the machine
Inputs:         NONE
Results:        integer representing SP level for .NET Framework 3.0
******************************************************************/
int CDetectFxRegistry::GetNetfx30SPLevel()
{
	int rc = -1;
	DWORD dwRegValue = 0;

	if (RegistryGetValue(HKEY_LOCAL_MACHINE, 
						 g_szNetfx30RegKeyName, 
						 g_szNetfx30SPxRegValueName, 
						 NULL, 
						 (LPBYTE)&dwRegValue, 
						 sizeof(DWORD)))
	{
		rc = (int)dwRegValue;
	}

	// We return -1 if the .NET Framework 3.0 is not installed or there was 
	// some kind of error retrieving the data from the registry
	return rc;
}


/******************************************************************
Function Name:	GetNetfx35SPLevel
Description:	Uses the detection method recommended at
                http://blogs.msdn.com/astebner/archive/2004/09/14/229574.aspx
                to determine what service pack for the 
                .NET Framework 3.5 is installed on the machine
Inputs:         NONE
Results:        integer representing SP level for .NET Framework 3.5
******************************************************************/
int CDetectFxRegistry::GetNetfx35SPLevel()
{
	int rc = -1;
	DWORD dwRegValue = 0;

	if (RegistryGetValue(HKEY_LOCAL_MACHINE, 
						 g_szNetfx35RegKeyName, 
						 g_szNetfx35SPxRegValueName, 
						 NULL, 
						 (LPBYTE)&dwRegValue, 
						 sizeof(DWORD)))
	{
		rc = (int)dwRegValue;
	}

	// We return -1 if the .NET Framework 3.5 is not installed or there was 
	// some kind of error retrieving the data from the registry
	return rc;
}


/******************************************************************
Function Name:	CheckNetfx30BuildNumber
Description:	Retrieves the .NET Framework 3.0 build number from
                the registry and validates that it is not a pre-release
                version number
Inputs:         NONE
Results:        true if the build number is greater than or equal
                to 3.0.04506.25; false otherwise
******************************************************************/
bool CDetectFxRegistry::CheckNetfx30BuildNumber()
{
	TCHAR szRegValue[MAX_PATH] = { _T('\0') };
	TCHAR *pszToken = NULL;
	int nVersionPartCounter = 0;
	int nVersionMajor = 0;
	int nVersionMinor = 0;
	int nBuildNumber = 0;
	int nRevisionNumber = 0;
	bool bRegistryRetVal = false;
	bool rc = false;

	// Attempt to retrieve the build number registry value
	bRegistryRetVal = RegistryGetValue(HKEY_LOCAL_MACHINE, 
									   g_szNetfx30RegKeyName, 
									   g_szNetfx30VersionRegValueName, 
									   NULL, 
									   (LPBYTE)szRegValue, 
									   sizeof(szRegValue)-2);

	if (bRegistryRetVal)
	{
		// This registry value should be of the format
		// #.#.#####.##.  Try to parse the 4 parts of
		// the version here
		pszToken = _tcstok(szRegValue, _T("."));
		while (NULL != pszToken)
		{
			nVersionPartCounter++;

			switch (nVersionPartCounter)
			{
				case 1:
					// Convert the major version value to an integer
					nVersionMajor = _ttoi(pszToken);
					break;

				case 2:
					// Convert the major version value to an integer
					nVersionMinor = _ttoi(pszToken);
					break;

				case 3:
					// Convert the major version value to an integer
					nBuildNumber = _ttoi(pszToken);
					break;

				case 4:
					// Convert the major version value to an integer
					nRevisionNumber = _ttoi(pszToken);
					break;

				default:
					break;
			}

			// Get the next part of the version number
			pszToken = _tcstok(NULL, _T("."));
		}
	}

	// Compare the version number retrieved from the registry with
	// the version number of the final release of the .NET Framework 3.0
	if (nVersionMajor > g_nNetfx30VersionMajor)
	{
		rc = true;
	}
	else if (nVersionMajor == g_nNetfx30VersionMajor)
	{
		if (nVersionMinor > g_nNetfx30VersionMinor)
		{
			rc = true;
		}
		else if (nVersionMinor == g_nNetfx30VersionMinor)
		{
			if (nBuildNumber > g_nNetfx30BuildNumber)
			{
				rc = true;
			}
			else if (nBuildNumber == g_nNetfx30BuildNumber)
			{
				if (nRevisionNumber >= g_nNetfx30RevisionNumber)
				{
					rc = true;
				}
			}
		}
	}

	// return false if the version in the registry is less than the
	// version of the final release of the .NET Framework 3.0

	return rc;
}

/******************************************************************
Function Name:	CheckNetfx35BuildNumber
Description:	Retrieves the .NET Framework 3.5 build number from
                the registry and validates that it is not a pre-release
                version number
Inputs:         NONE
Results:        true if the build number is greater than or equal
                to 3.5.20404.00; false otherwise
******************************************************************/
bool CDetectFxRegistry::CheckNetfx35BuildNumber()
{
	TCHAR szRegValue[MAX_PATH] = { _T('\0') };
	TCHAR *pszToken = NULL;
	int nVersionPartCounter = 0;
	int nVersionMajor = 0;
	int nVersionMinor = 0;
	int nBuildNumber = 0;
	int nRevisionNumber = 0;
	bool bRegistryRetVal = false;
	bool rc = false;

	// Attempt to retrieve the build number registry value
	bRegistryRetVal = RegistryGetValue(HKEY_LOCAL_MACHINE, 
									   g_szNetfx35RegKeyName, 
									   g_szNetfx35VersionRegValueName, 
									   NULL, 
									   (LPBYTE)szRegValue, 
									   sizeof(szRegValue)-2);

	if (bRegistryRetVal)
	{
		// This registry value should be of the format
		// #.#.#####.##.  Try to parse the 4 parts of
		// the version here
		pszToken = _tcstok(szRegValue, _T("."));
		while (NULL != pszToken)
		{
			nVersionPartCounter++;

			switch (nVersionPartCounter)
			{
				case 1:
					// Convert the major version value to an integer
					nVersionMajor = _ttoi(pszToken);
					break;

				case 2:
					// Convert the major version value to an integer
					nVersionMinor = _ttoi(pszToken);
					break;

				case 3:
					// Convert the major version value to an integer
					nBuildNumber = _ttoi(pszToken);
					break;

				case 4:
					// Convert the major version value to an integer
					nRevisionNumber = _ttoi(pszToken);
					break;

				default:
					break;
			}

			// Get the next part of the version number
			pszToken = _tcstok(NULL, _T("."));
		}
	}

	// Compare the version number retrieved from the registry with
	// the version number of the final release of the .NET Framework 3.5
	if (nVersionMajor > g_nNetfx35VersionMajor)
	{
		rc = true;
	}
	else if (nVersionMajor == g_nNetfx35VersionMajor)
	{
		if (nVersionMinor > g_nNetfx35VersionMinor)
		{
			rc = true;
		}
		else if (nVersionMinor == g_nNetfx35VersionMinor)
		{
			if (nBuildNumber > g_nNetfx35BuildNumber)
			{
				rc = true;
			}
			else if (nBuildNumber == g_nNetfx35BuildNumber)
			{
				if (nRevisionNumber >= g_nNetfx35RevisionNumber)
				{
					rc = true;
				}
			}
		}
	}

	// return false if the version in the registry is less than the
	// version of the final release of the .NET Framework 3.5

	return rc;
}


bool CDetectFxRegistry::IsCurrentOSTabletMedCenter()
{
	// Use GetSystemMetrics to detect if we are on a Tablet PC or Media Center OS  
	return ((GetSystemMetrics(SM_TABLETPC) != 0) || (GetSystemMetrics(SM_MEDIACENTER) != 0));
}


/******************************************************************
Function Name:	RegistryGetValue
Description:	Get the value of a reg key
Inputs:			HKEY hk - The hk of the key to retrieve
				TCHAR *pszKey - Name of the key to retrieve
				TCHAR *pszValue - The value that will be retrieved
				DWORD dwType - The type of the value that will be retrieved
				LPBYTE data - A buffer to save the retrieved data
				DWORD dwSize - The size of the data retrieved
Results:		true if successful, false otherwise
******************************************************************/
bool CDetectFxRegistry::RegistryGetValue(HKEY hk, 
										 const TCHAR * pszKey, 
										 const TCHAR * pszValue, 
										 DWORD dwType, 
										 LPBYTE data, 
										 DWORD dwSize)
{
	bool rc = false;
	HKEY hkOpened = 0;
	HKEY hkRemote = 0;
	DWORD dwDataType = dwType;

	if (m_szComputer[0] != _T('\0'))
	{
		if (RegConnectRegistry(m_szComputer, hk, &hkRemote) != ERROR_SUCCESS)
		{
#ifdef XLISTBOX_H
			if (m_pList)
				m_pList->Printf(CXListBox::Red, CXListBox::White, 0, 
					_T("\tcannot open registry on %s"), m_szComputer);
#endif
			return false;
		}
		hk = hkRemote;		// set key for remote registry

#ifdef XLISTBOX_H
		if (m_pList)
			m_pList->Printf(CXListBox::Black, CXListBox::White, 0, 
				_T("\tconnected to registry on %s"), m_szComputer);
#endif
	}

#ifdef XLISTBOX_H
	if (m_pList)
		m_pList->Printf(CXListBox::Black, CXListBox::White, 0, 
			_T("\tlooking for key HKLM\\%s"), pszKey);
#endif

	if (RegOpenKeyEx(hk, pszKey, 0, KEY_READ, &hkOpened) == ERROR_SUCCESS)
	{
#ifdef XLISTBOX_H
		if (m_pList)
			m_pList->Printf(CXListBox::Black, CXListBox::White, 0, 
				_T("\tlooking for value %s"), pszValue);
#endif

		// If the key was opened, try to retrieve the value
		if (RegQueryValueEx(hkOpened, pszValue, 0, &dwDataType, 
				(LPBYTE)data, &dwSize) == ERROR_SUCCESS)
		{
			rc = true;

			if (dwDataType == REG_DWORD)
			{
#ifdef XLISTBOX_H
				if (m_pList)
					m_pList->Printf(CXListBox::Green, CXListBox::White, 0, 
					_T("\tvalue = %u"), *data);
#endif
			}
			else if (dwDataType == REG_SZ ||
					 dwDataType == REG_EXPAND_SZ ||
					 dwDataType == REG_MULTI_SZ)
			{
#ifdef XLISTBOX_H
				if (m_pList)
					m_pList->Printf(CXListBox::Green, CXListBox::White, 0, 
					_T("\tvalue = %s"), data);
#endif
			}
		}
		else
		{
#ifdef XLISTBOX_H
			if (m_pList)
				m_pList->Printf(CXListBox::Red, CXListBox::White, 0, 
					_T("\tvalue %s does not exist"), pszValue);
#endif
		}
		RegCloseKey(hkOpened);		// close key for value

	}
	else
	{
#ifdef XLISTBOX_H
		if (m_pList)
			m_pList->Printf(CXListBox::Red, CXListBox::White, 0, 
				_T("\tkey HKLM\\%s does not exist"), pszKey);
#endif
	}

	if (hkRemote)
	{
		RegCloseKey(hkRemote);		// disconnect from remote registry
	}

	return rc;
}
